﻿
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Collections;

namespace System.Linq
{
    /// <summary>
    /// 对象扩展
    /// </summary>
    public static class ObjectExtension
    {
        /// <summary>
        /// 是否为空对象
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsNull<TSource>(this TSource obj)
        {
            var returnValue = obj == null;
            if (returnValue) return returnValue;

            var typeName = obj.GetType().Name.ToLower();
            switch (typeName)
            {
                case "string":
                    {
                        var value = obj.ToString();
                        returnValue = string.IsNullOrEmpty(value) || value == "";
                    }
                    break;
                case "list`1":
                    {
                        var values = ((IEnumerable)obj).Cast<object>();
                        returnValue = !values.Any();
                    }
                    break;
            }

            return returnValue;
        }

        /// <summary>
        /// 是否不是空对象
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsNotNull<TSource>(this TSource obj)
        {
            return !obj.IsNull<TSource>();
        }

        /// <summary>
        /// 获得对象的属性值
        /// </summary>
        /// <typeparam name="TSource">对象类型</typeparam>
        /// <typeparam name="TProperty">对象类型的属性类型</typeparam>
        /// <param name="obj">操作的对象</param>
        /// <param name="predicate">操作用的表达式</param>
        /// <returns></returns>
        public static TProperty SelectOrDefault<TSource, TProperty>(this TSource obj, Expression<Func<TSource, TProperty>> predicate)
        {
            if (obj.IsNull()) return default(TProperty);

            var blockExpression = predicate.Body as MethodCallExpression;
            if (blockExpression.IsNotNull())
                return (TProperty)blockExpression.Method.Invoke(obj, new object[] { obj });

            var bodyExpression = predicate.Body as MemberExpression;
            if (bodyExpression.IsNotNull())
            {
                var targetValue = obj.GetType().GetProperties().FirstOrDefault(p => p.Name == bodyExpression.Member.Name).GetValue(obj, null);
                if (targetValue.IsNull()) return default(TProperty);

                return (TProperty)targetValue;
            }

            throw new FormatException("表达式格式不正确，应该是属性或者方法！");
        }

        /// <summary>
        /// 具有Null判断的ToString
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="defaultValue">默认空字符</param>
        /// <returns>对象为空时，返回String.Empty；不为空，返回对象的ToString()</returns>
        public static string ToStr(this object obj, string defaultValue = "")
        {
            if (obj.IsNull()) return defaultValue;

            return obj.ToString();
        }

        /// <summary>
        /// 是否为可空类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static bool IsNullableType(this Type obj)
        {
            return obj.Name.StartsWith("Nullable");
        }

        /// <summary>
        /// 获取值类型
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static Type ToValueType(this Type obj)
        {
            return obj.IsNullableType() ? Nullable.GetUnderlyingType(obj) : obj;
        }

        /// <summary>
        /// 对象序列化为Json字符
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string ToJsonString(this object obj)
        {
            return JsonConvert.SerializeObject(obj);
        }

        /// <summary>
        /// 将当前对象原样克隆成新对象
        /// </summary>
        /// <typeparam name="TSource">新对象类型</typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static TSource CloneObject<TSource>(this object obj) where TSource : new()
        {
            return obj.CloneObject<TSource>(new List<string>());
        }

        /// <summary>
        /// 将当前对象原样克隆成新对象
        /// </summary>
        /// <typeparam name="TSource">新对象类型</typeparam>
        /// <param name="obj"></param>
        /// <param name="exceptPropertyNames">排除复制的字段，不区分大小写</param>
        /// <returns></returns>
        public static TSource CloneObject<TSource>(this object obj, List<string> exceptPropertyNames) where TSource : new()
        {
            var t = new TSource();

            obj.CloneObject(t, exceptPropertyNames);

            return t;
        }

        /// <summary>
        /// 将当前对象原样克隆成新对象
        /// </summary>
        /// <typeparam name="TSource">新对象类型</typeparam>
        /// <param name="obj"></param>
        /// <param name="convertPredicate">转换回调函数</param>
        /// <returns></returns>
        public static TSource CloneObject<TSource>(this object obj, Action<TSource> convertPredicate) where TSource : new()
        {
            return obj.CloneObject<TSource>(new List<string>(), convertPredicate);
        }

        /// <summary>
        /// 将当前对象原样克隆成新对象
        /// </summary>
        /// <typeparam name="TSource">新对象类型</typeparam>
        /// <param name="obj"></param>
        /// /// <param name="exceptPropertyNames">排除复制的字段，不区分大小写</param>
        /// <param name="convertPredicate">转换回调函数</param>        
        /// <returns></returns>
        public static TSource CloneObject<TSource>(this object obj, List<string> exceptPropertyNames, Action<TSource> convertPredicate) where TSource : new()
        {
            var t = obj.CloneObject<TSource>(exceptPropertyNames);

            convertPredicate(t);

            return t;
        }

        /// <summary>
        /// 将当前对象克隆到<para>toSource</para>
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <param name="toSource"></param>
        public static void CloneObject<TSource>(this object obj, TSource toSource)
        {
            obj.CloneObject(toSource, new List<string>());
        }

        /// <summary>
        /// 将当前对象克隆到<para>toSource</para>
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="obj"></param>
        /// <param name="toSource"></param>
        /// <param name="exceptPropertyNames">排除复制的字段，不区分大小写</param>
        public static void CloneObject<TSource>(this object obj, TSource toSource, List<string> exceptPropertyNames)
        {
            var tTypes = toSource.GetType().GetProperties().Where(p => p.CanWrite);

            if (exceptPropertyNames.IsNull())
            {
                exceptPropertyNames = new List<string>();
            }
            exceptPropertyNames = exceptPropertyNames.Select(p => p.ToLower()).ToList();

            obj.GetType()
                .GetProperties()
                .Where(p => p.CanRead)
                .Where(p => !exceptPropertyNames.Contains(p.Name.ToLower()))
                .ForEach(p =>
                {
                    var newP = tTypes.FirstOrDefault(pi => pi.Name == p.Name);
                    if (newP.IsNotNull())
                    {
                        newP.SetValue(toSource, p.GetValue(obj, null), null);
                    }
                });
        }

        /// <summary>
        /// 将对象转换为<typeparamref name="TSource"/>类型对象
        /// </summary>
        /// <typeparam name="TSource">目标对象类型</typeparam>
        /// <param name="obj">转换对象</param>
        /// <param name="defaultValue">转换失败的默认值</param>
        /// <returns>安全的返回目标对象值</returns>
        public static TSource To<TSource>(this object obj, TSource defaultValue = default(TSource))
        {
            var sourceType = typeof(TSource);

            if (sourceType != typeof(int)
                && sourceType != typeof(string)
                && sourceType != typeof(bool)
                && sourceType != typeof(float)
                && sourceType != typeof(double)
                && sourceType != typeof(long)
                && sourceType != typeof(decimal)
                && sourceType != typeof(DateTime)
                && sourceType != typeof(Guid)
                && !sourceType.IsEnum)
            {
                throw new ArgumentOutOfRangeException("该方法只适用转换为类型为：int, string, bool, float, double, long, decimal, DateTime, Guid, Enum");
            }

            if (obj.IsNull()) return defaultValue;

            var targetType = sourceType.IsEnum ? typeof(Enum) : sourceType;
            var tryParseMethod = targetType.GetMethods().FirstOrDefault(p => p.Name == "TryParse" && p.ReturnType == typeof(bool));
            if (tryParseMethod.IsNull()) return defaultValue;

            //bool处理1、0
            var objValue = obj.ToStr();
            if ((objValue == "1" || objValue == "0") && sourceType == typeof(bool))
            {
                objValue = objValue == "1" ? "True" : "False";
            }

            try
            {
                var invokeArgs = new object[] { objValue, default(TSource) };
                if (tryParseMethod.ContainsGenericParameters)
                {
                    tryParseMethod = tryParseMethod.MakeGenericMethod(sourceType);
                }
                var result = (bool)tryParseMethod.Invoke(null, invokeArgs);

                if (result && invokeArgs[1].GetType() == typeof(TSource))
                {
                    return (TSource)invokeArgs[1];
                }
            }
            catch
            {

            }

            return defaultValue;
        }

        /// <summary>
        /// 是否为<typeparamref name="TSource"/>对象
        /// </summary>
        /// <typeparam name="TSource">目标对象类型</typeparam>
        /// <param name="obj">判断对象</param>
        /// <returns>返回判断结果</returns>
        public static bool Is<TSource>(this object obj)
        {
            if (obj.IsNull()) return false;

            if (typeof(TSource) == typeof(bool))
            {
                var objStr = obj.ToStr().ToLower();
                if (objStr == "y" || objStr == "yes" || objStr == "1") return true;
            }

            var tryParseMethod = typeof(TSource)
                .GetMethods()
                .FirstOrDefault(p => p.Name == "TryParse" && p.ReturnType == typeof(bool));
            if (tryParseMethod.IsNull()) return false;

            if ((bool)tryParseMethod.Invoke(null, new object[] { obj, default(TSource) })) return true;

            try
            {
                return ((TSource)obj).IsNotNull();
            }
            catch
            {
                return false;
            }
        }
    }
}
